Let’s triangulate marker genes for each of the lineages that we find enrichment in. We have three sources of information:

  1. Marker genes by DESeq on pseudobulk samples
  2. Correlation with abundance across 89 samples

Let’s take the Intersection where we take the intersect of loosely filtered results multiple approaches

These genes will be used as a feature space for manual pseudobulk-based regression

N.B. these are lineages derived from consensus clustering on NMF weights This dimensionality reduction approach actually lead to cleaner clustering of celltypes than just clustering celltypes by correlation

Setup

library(tidyverse)
library(ggpubr)

Code from AUCell for adaptive thresholding

library(AUCell)

### Helper function for AUC thresholding from AUCell
get_Threshold <- function (auc, gSetName, plotHist = TRUE, smallestPopPercent = 0.25, 
          densAdjust = 2, thrP = 0.01, nBreaks = 100) 
{
  aucRow <- t(as.matrix(auc))
  #gSetName <- rownames(aucRow)[1]
  nCells <- length(auc)
  skipGlobal <- TRUE
  skipRed <- FALSE
  skipSmallDens <- FALSE
  commentMsg <- ""
  aucThrs <- c()
  notPopPercent <- 1 - smallestPopPercent
  if (sum(auc == 0) > (nCells * notPopPercent)) {
    skipGlobal <- FALSE
    commentMsg <- paste(commentMsg, round((sum(auc == 0)/nCells) * 
                                            100), "% (more than ", notPopPercent, "%) of AUC are zero. ", 
                        sep = "")
  }
  meanAUC <- mean(auc)
  sdAUC <- sd(auc)
  maybeNormalDistr <- !suppressWarnings(ks.test(auc, rnorm(max(100, 
                                                               length(auc)), mean = meanAUC, sd = sdAUC), alternative = "less")$p.value < 
                                          0.01)
  if (maybeNormalDistr) {
    commentMsg <- paste0(commentMsg, "The AUC might follow a normal distribution (random gene-set?). ")
    skipGlobal <- FALSE
    aucThrs["outlierOfGlobal"] <- qnorm(1 - (thrP/nCells), 
                                        mean = meanAUC, sd = sdAUC)
  }
  histogram <- hist(c(0, auc/max(auc)), breaks = 100, plot = FALSE)$count
  if ((sum(histogram[1:5])/sum(histogram)) >= notPopPercent * 
      0.75) {
    skipGlobal <- FALSE
    skipRed <- TRUE
    skipSmallDens <- TRUE
  }
  if ((sum(histogram[1:10])/sum(histogram)) >= notPopPercent * 
      0.5) {
    skipSmallDens <- TRUE
    skipGlobal <- FALSE
    aucThrs["tenPercentOfMax"] <- max(auc) * 0.1
  }
  densCurve <- density(auc, adjust = densAdjust, cut = 0)
  maximumsDens <- NULL
  inflPoints <- diff(sign(diff(densCurve$y)))
  maximumsDens <- which(inflPoints == -2)
  globalMax <- maximumsDens[which.max(densCurve$y[maximumsDens])]
  minimumDens <- which(inflPoints == 2)
  smallMin <- NULL
  if (!skipSmallDens) 
    smallMin <- data.table::last(minimumDens[which(minimumDens < 
                                                     globalMax)])
  minimumDens <- c(smallMin, minimumDens[which(minimumDens > 
                                                 globalMax)])
  densTrh <- NULL
  if (length(minimumDens) > 0) {
    densTrh <- densCurve$x[min(minimumDens)]
    if (length(maximumsDens) > 0) {
      nextMaxs <- maximumsDens[which(densCurve$x[maximumsDens] > 
                                       densTrh)]
      if ((max(densCurve$y[nextMaxs])/max(densCurve$y)) < 
          0.05) {
        densTrh <- NULL
      }
    }
  }
  auc <- sort(auc)
  distrs <- list()
  distrs[["Global_k1"]] <- list(mu = c(meanAUC, NA), sigma = c(sdAUC, 
                                                               NA), x = auc)
  if ("mixtools" %in% rownames(installed.packages())) {
    na <- capture.output(distrs[["k2"]] <- tryCatch(mixtools::normalmixEM(auc, 
                                                                          fast = FALSE, k = 2, verb = FALSE), error = function(e) {
                                                                            return(NULL)
                                                                          }))
    na <- capture.output(distrs[["k3"]] <- tryCatch(mixtools::normalmixEM(auc, 
                                                                          fast = FALSE, k = 3, verb = FALSE), error = function(e) {
                                                                            return(NULL)
                                                                          }))
    if (is.null(distrs[["k2"]]) && is.null(distrs[["k3"]])) {
      if (sum(auc == 0) < (nCells * notPopPercent * 0.5)) 
        skipGlobal <- FALSE
    }
    if (!is.null(distrs[["k2"]])) {
      compL <- which.min(distrs[["k2"]][["mu"]])
      compR <- which.max(distrs[["k2"]][["mu"]])
      height1 <- 0.4/distrs[["k2"]][["sigma"]][compL] * 
        distrs[["k2"]][["lambda"]][compL]
      height2 <- 0.4/distrs[["k2"]][["sigma"]][compR] * 
        distrs[["k2"]][["lambda"]][compR]
      taller <- height1 < height2
      globalInclInFirst <- (distrs[["Global_k1"]]$mu[1] < 
                              (distrs[["k2"]][["mu"]][compL] + (1.5 * distrs[["k2"]][["sigma"]][compL])))
      includedInGlobal <- ((distrs[["k2"]][["mu"]][compL] > 
                              (distrs[["Global_k1"]]$mu[1] - distrs[["Global_k1"]]$sigma[1])) && 
                             (distrs[["k2"]][["mu"]][compR] < (distrs[["Global_k1"]]$mu[1] + 
                                                                 distrs[["Global_k1"]]$sigma[1])))
      if (taller || (globalInclInFirst && includedInGlobal)) {
        skipGlobal <- FALSE
        if (globalInclInFirst && includedInGlobal) 
          commentMsg <- paste(commentMsg, "The global distribution overlaps the partial distributions. ")
        if (taller && !includedInGlobal) 
          commentMsg <- paste(commentMsg, "The right distribution is taller. ")
      }
    }
  }
  else {
    warning("Package 'mixtools' is not available to calculate the sub-distributions.")
  }
  glProb <- 1 - (thrP/nCells + smallestPopPercent)
  aucThrs["Global_k1"] <- qnorm(glProb, mean = distrs[["Global_k1"]][["mu"]][1], 
                                sd = distrs[["Global_k1"]][["sigma"]][1])
  if (!is.null(distrs[["k2"]])) {
    k2_L <- which.min(distrs[["k2"]][["mu"]])
    aucThrs["L_k2"] <- qnorm(1 - (thrP/nCells), mean = distrs[["k2"]][["mu"]][k2_L], 
                             sd = distrs[["k2"]][["sigma"]][k2_L])
  }
  if (!is.null(distrs[["k3"]])) {
    k3_R <- which.max(distrs[["k3"]][["mu"]])
    k3_R_threshold <- qnorm(thrP, mean = distrs[["k3"]][["mu"]][k3_R], 
                            sd = distrs[["k3"]][["sigma"]][k3_R])
    if (k3_R_threshold > 0) 
      aucThrs["R_k3"] <- k3_R_threshold
  }
  if (!is.null(densTrh)) {
    aucThrs["minimumDens"] <- densTrh
  }
  aucThr <- aucThrs
  if (skipGlobal) 
    aucThr <- aucThrs[which(!names(aucThrs) %in% "Global_k1")]
  if (skipRed) 
    aucThr <- aucThrs[which(!names(aucThrs) %in% "L_k2")]
  aucThr <- aucThr[which.max(aucThr)]
  if ((length(aucThr) > 0) && (names(aucThr) == "minimumDens")) {
    maximumsDens <- maximumsDens[which(densCurve$y[maximumsDens] > 
                                         1)]
    if (length(maximumsDens) > 2) {
      tmp <- cbind(minimumDens[seq_len(length(maximumsDens) - 
                                         1)], maximumsDens[-1])
      FCs <- densCurve$y[tmp[, 2]]/densCurve$y[tmp[, 1]]
      if (any(FCs > 1.5)) 
        warning(gSetName, ":\tCheck the AUC histogram. ", 
                "'minimumDens' was selected as the best threshold, ", 
                "but there might be several distributions in the AUC.")
    }
  }
  if ("minimumDens" %in% names(aucThrs)) 
    aucThr <- aucThrs["minimumDens"]
  if (length(aucThr) == 0) 
    aucThr <- aucThrs[which.max(aucThrs)]
  if (length(aucThr) == 0) 
    aucThr <- 1
  if (length(aucThr) > 1) 
    aucThr <- unlist(aucThr[which.max(aucThr)])
  if (plotHist) {
    histInfo <- AUCell_plotHist(aucRow, aucThr = aucThr, 
                                nBreaks = nBreaks)
    histMax <- max(histInfo[[gSetName]]$counts)
    densCurve$y <- densCurve$y * (histMax/max(densCurve$y))
    thisLwd <- ifelse((aucThrs["minimumDens"] == aucThr) && 
                        (!is.null(aucThr) && !is.null(aucThrs["minimumDens"])), 
                      3, 1)
    lines(densCurve, lty = 1, lwd = thisLwd, col = "blue")
    if (!is.null(minimumDens)) 
      points(densCurve$x[minimumDens], densCurve$y[minimumDens], 
             pch = 16, col = "darkblue")
    scalFact <- 1
    aucDistr <- dnorm(distrs[["Global_k1"]][["x"]], mean = distrs[["Global_k1"]][["mu"]][1], 
                      sd = distrs[["Global_k1"]][["sigma"]][1])
    scalFact <- (histMax/max(aucDistr)) * 0.95
    thisLwd <- ifelse(aucThrs["Global_k1"] == aucThr, 3, 
                      1)
    lines(distrs[["Global_k1"]][["x"]], scalFact * aucDistr, 
          col = "darkgrey", lwd = thisLwd, lty = 2)
    if (!is.null(distrs[["k2"]])) {
      aucDistr <- dnorm(distrs[["k2"]][["x"]], mean = distrs[["k2"]][["mu"]][k2_L], 
                        sd = distrs[["k2"]][["sigma"]][k2_L])
      scalFact <- (histMax/max(aucDistr)) * 0.95
      thisLwd <- ifelse(aucThrs["k2"] == aucThr, 3, 1)
      lines(distrs[["k2"]][["x"]], scalFact * aucDistr, 
            col = "red", lwd = thisLwd, lty = 2)
      rect(distrs[["k2"]][["mu"]][k2_L] - distrs[["k2"]][["sigma"]][k2_L], 
           histMax - (histMax * 0.02), distrs[["k2"]][["mu"]][k2_L] + 
             distrs[["k2"]][["sigma"]][k2_L], histMax, col = "#70000030", 
           border = "#00009000")
    }
    if ((!is.null(distrs[["k3"]])) && ("R_k3" %in% names(aucThrs))) {
      k3_L <- which.min(distrs[["k3"]][["mu"]])
      aucDistr2 <- dnorm(distrs[["k3"]][["x"]], mean = distrs[["k3"]][["mu"]][k3_R], 
                         sd = distrs[["k3"]][["sigma"]][k3_R])
      scalFact2 <- scalFact * (distrs[["k3"]][["lambda"]][k3_R]/distrs[["k3"]][["lambda"]][k3_L])
      thisLwd <- ifelse(aucThrs["k3"] == aucThr, 3, 1)
      lines(distrs[["k3"]][["x"]], scalFact2 * aucDistr2, 
            col = "magenta", lwd = thisLwd, lty = 2)
      rect(distrs[["k3"]][["mu"]][k3_R] - distrs[["k3"]][["sigma"]][k3_R], 
           histMax - (histMax * 0.02), distrs[["k3"]][["mu"]][k3_R] + 
             distrs[["k3"]][["sigma"]][k3_R], histMax, col = "#80808030", 
           border = "#80808030")
    }
    aucThrs <- aucThrs[!is.na(aucThrs)]
    if (length(aucThrs) > 0) {
      pars <- list()
      pars[["Global_k1"]] <- c(col1 = "#909090", col2 = "black", 
                               pos = 0.9)
      pars[["L_k2"]] <- c(col1 = "red", col2 = "darkred", 
                          pos = 0.8)
      pars[["R_k3"]] <- c(col1 = "magenta", col2 = "magenta", 
                          pos = 0.6)
      pars[["minimumDens"]] <- c(col1 = "blue", col2 = "darkblue", 
                                 pos = 0.4)
      pars[["tenPercentOfMax"]] <- c(col1 = "darkgreen", 
                                     col2 = "darkgreen", pos = 0.9)
      pars[["outlierOfGlobal"]] <- c(col1 = "darkgreen", 
                                     col2 = "darkgreen", pos = 0.9)
      for (thr in names(aucThrs)) {
        thisLwd <- ifelse(aucThrs[thr] == aucThr, 5, 
                          2)
        thisLty <- ifelse(aucThrs[thr] == aucThr, 1, 
                          3)
        abline(v = aucThrs[thr], col = pars[[thr]][1], 
               lwd = thisLwd, lty = thisLty)
        xPos <- aucThrs[thr] * 1.01
        if (aucThrs[thr] > (max(auc) * 0.8)) 
          xPos <- 0
        if (aucThrs[thr] == aucThr) 
          text(xPos, histMax * as.numeric(pars[[thr]][3]), 
               pos = 4, col = pars[[thr]][2], cex = 0.8, 
               paste("AUC > ", signif(aucThrs[thr], 2), 
                     "\n(", sum(auc > aucThrs[thr]), " cells)", 
                     sep = ""))
      }
    }
  }
  return(list(selected = aucThr, thresholds = cbind(threshold = aucThrs, 
                                                    nCells = sapply(aucThrs, function(x) sum(auc > x))), 
              comment = commentMsg))
}

DE Genes

library(Seurat)
Attaching SeuratObject
library(tidyverse)
Lineage_DE <- read_csv("BALL_DEresults_NMF_Lineage.csv")

── Column specification ───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
cols(
  Gene = col_character(),
  baseMean = col_double(),
  log2FoldChange = col_double(),
  lfcSE = col_double(),
  stat = col_double(),
  pvalue = col_double(),
  padj = col_double(),
  Lineage = col_character()
)
Lineage_DE
Lineage_DE %>% 
  filter(padj < 0.01) %>% 
  pull(Lineage) %>% table() %>% sort(decreasing = T)
.
         Erythroid               T_NK              Pro_B              Pre_B           Mature_B           Monocyte Myeloid_Progenitor    MLP_CLP_PreProB            Pre_pDC 
             12871              12507               9623               8721               7703               7186               4309               1829                826 
      HSC_MPP_LMPP 
               469 

For NMF score Quantification

Pearson correlation VST NMF stringent Intersect on Pseudobulk DE FDR 0.05

Or LASSO regression Pearson correlation stringent VST NMF + Pseudobulk DE stringent

Set adaptive thresholds
Ths is the pearson correlation between VST-normalized gene expression and the score for each NMF lineage

NMF_corr <- data.table::fread('NMF_gene_corr.csv') %>% select(-V1)
Warning: Detected 5 column names but the data has 6 columns (i.e. invalid file). Added 1 extra default column name for the first column which is guessed to be row names or an index. Use setnames() afterwards if this guess is not correct, or fix the file write command that created the file to create a valid file.
NMF_corr
NMF_corr %>% 
  filter(qvalue < 0.05, pearson > 0) %>% 
  pull(NMF) %>% table() %>% sort(decreasing = TRUE)
.
 NMF4 NMF10  NMF7  NMF8  NMF2  NMF1  NMF6  NMF9  NMF3  NMF5 
 3085  1617  1286  1270  1143  1135  1037   935   805   636 
Set Positive Correlation Thresholds
NMF_corr_thresholds = data.frame()

for(lin in unique(NMF_corr$NMF)){
  thresholds = get_Threshold(NMF_corr %>% filter(NMF == lin, qvalue < 0.05, pearson > 0) %>% pull(pearson), lin)$thresholds
  NMF_corr_thresholds = 
    bind_rows(
      NMF_corr_thresholds,
      data.frame(
        'NMF' = lin,
        'K1_threshold' = thresholds['Global_k1','threshold'],
        'K2_threshold' = thresholds['L_k2','threshold']
    ))
}
Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Visualize thresholding within all positive correlations

NMF_corr_thresholds %>% 
  left_join(NMF_corr) %>% filter(pearson > 0) %>% 
  mutate(NMF = factor(NMF, levels = c('NMF6', 'NMF8', 'NMF2', 'NMF1', 'NMF3', 'NMF9', 'NMF4',
                                      'NMF5', 'NMF10', 'NMF7'))) %>% 
  mutate(threshold = ifelse(pearson > K1_threshold, 'pass', 'fail')) %>% 
  ggplot(aes(x = pearson, fill = threshold)) + 
  geom_histogram(bins=100) + theme_pubr(legend = 'top') + 
  scale_fill_brewer(palette = 'Dark2', direction = -1) + 
  facet_wrap(.~NMF, scale = 'free', ncol=5) + 
  geom_vline(aes(xintercept = K1_threshold), lty=2)
Joining with `by = join_by(NMF)`Warning: Each row in `x` is expected to match at most 1 row in `y`.

Visualize thresholding within FDR < 0.05 correlations

NMF_corr_thresholds %>% 
  left_join(NMF_corr) %>% filter(qvalue < 0.05, pearson > 0) %>% 
  mutate(NMF = factor(NMF, levels = c('NMF6', 'NMF8', 'NMF2', 'NMF1', 'NMF3', 'NMF9', 'NMF4',
                                      'NMF5', 'NMF10', 'NMF7'))) %>% 
  mutate(threshold = ifelse(pearson > K1_threshold, 'pass', 'fail')) %>% 
  ggplot(aes(x = pearson, fill = threshold)) + 
  geom_histogram(bins=100) + theme_pubr(legend = 'top') + 
  scale_fill_brewer(palette = 'Dark2', direction = -1) + 
  facet_wrap(.~NMF, scale = 'free', ncol=5) + 
  geom_vline(aes(xintercept = K1_threshold), lty=2)
Joining with `by = join_by(NMF)`Warning: Each row in `x` is expected to match at most 1 row in `y`.

NMF_corr_pos <- NMF_corr_thresholds %>% 
  left_join(NMF_corr) %>% 
  mutate(threshold = ifelse(pearson > K1_threshold, 'pass', 'fail')) 
Joining with `by = join_by(NMF)`Warning: Each row in `x` is expected to match at most 1 row in `y`.
NMF_corr_pos %>% 
  filter(qvalue < 0.05, pearson > 0) %>% 
  filter(threshold == 'pass') %>% 
  pull(NMF) %>% table() %>% sort(decreasing = T)
.
 NMF4 NMF10  NMF7  NMF8  NMF1  NMF2  NMF6  NMF3  NMF9  NMF5 
  698   328   282   279   262   244   200   196   156   144 

Negative thresholds

NMF_corr_neg_thresholds = data.frame()

for(lin in unique(NMF_corr$NMF)){
  thresholds = get_Threshold(NMF_corr %>% filter(NMF == lin, qvalue < 0.05, pearson < 0) %>% mutate(pearson = -pearson) %>% pull(pearson), lin)$thresholds
  NMF_corr_neg_thresholds = 
    bind_rows(
      NMF_corr_neg_thresholds,
      data.frame(
        'NMF' = lin,
        'K1_threshold' = thresholds['Global_k1','threshold'],
        'K2_threshold' = thresholds['L_k2','threshold']
    ))
}

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Warning: no non-missing arguments to max; returning -Inf

Visualize thresholding within all negative correlations

NMF_corr_neg_thresholds %>% 
  left_join(NMF_corr) %>% filter(pearson < 0) %>% 
  mutate(negative_pearson = -pearson) %>% 
  mutate(NMF = factor(NMF, levels = c('NMF6', 'NMF8', 'NMF2', 'NMF1', 'NMF3', 'NMF9', 'NMF4',
                                      'NMF5', 'NMF11', 'NMF10', 'NMF7'))) %>% 
  mutate(threshold = ifelse(negative_pearson > K1_threshold, 'pass', 'fail')) %>% 
  ggplot(aes(x = negative_pearson, fill = threshold)) + 
  geom_histogram(bins=100) + theme_pubr(legend = 'top') + 
  scale_fill_brewer(palette = 'Dark2', direction = -1) + 
  facet_wrap(.~NMF, scale = 'free', ncol=6) + 
  geom_vline(aes(xintercept = K1_threshold), lty=2)
Joining with `by = join_by(NMF)`Warning: Each row in `x` is expected to match at most 1 row in `y`.

NMF_corr_neg_thresholds

Visualize thresholding within FDR < 0.05 correlations

NMF_corr_neg_thresholds %>% 
  left_join(NMF_corr) %>% filter(qvalue < 0.05, pearson < 0) %>% 
  mutate(negative_pearson = -pearson) %>% 
  mutate(NMF = factor(NMF, levels = c('NMF6', 'NMF8', 'NMF2', 'NMF1', 'NMF3', 'NMF9', 'NMF4',
                                      'NMF5', 'NMF11', 'NMF10', 'NMF7'))) %>% 
  mutate(threshold = ifelse(negative_pearson > K1_threshold, 'pass', 'fail')) %>% 
  ggplot(aes(x = negative_pearson, fill = threshold)) + 
  geom_histogram(bins=100) + theme_pubr(legend = 'top') + 
  scale_fill_brewer(palette = 'Dark2', direction = -1) + 
  facet_wrap(.~NMF, scale = 'free', ncol=6) + 
  geom_vline(aes(xintercept = K1_threshold), lty=2)
Joining with `by = join_by(NMF)`Warning: Each row in `x` is expected to match at most 1 row in `y`.

NMF_corr_neg_thresholds
NMF_corr_neg <- NMF_corr_neg_thresholds %>% 
  left_join(NMF_corr) %>% 
  mutate(threshold = ifelse(pearson < -K1_threshold, 'pass', 'fail')) 
Joining with `by = join_by(NMF)`Warning: Each row in `x` is expected to match at most 1 row in `y`.
NMF_corr_neg %>% 
  filter(qvalue < 0.05, pearson < 0) %>% 
  filter(threshold == 'pass') %>% 
  pull(NMF) %>% table() %>% sort(decreasing = T)
.
 NMF4  NMF7  NMF1  NMF2 NMF10  NMF8  NMF5  NMF6  NMF9  NMF3 
  821   399   392   312   252   178   173   130    94    80 
NMF_corr_neg %>% 
  filter(qvalue < 0.05, pearson < 0) 
NMF_corr_thresholding <- NMF_corr_pos %>% select(NMF, Gene, pearson, pvalue, qvalue, pos_K1_threshold = K1_threshold, pos_threshold = threshold) %>% 
  left_join(NMF_corr_neg %>% select(NMF, Gene, neg_K1_threshold = K1_threshold, neg_threshold = threshold)) %>% 
  mutate(neg_K1_threshold = -neg_K1_threshold, threshold = ifelse(pos_threshold == 'pass' | neg_threshold == 'pass', 'pass', 'fail')) %>% 
  select(NMF, Gene, pearson, pvalue, qvalue, pos_K1_threshold, neg_K1_threshold, threshold)
Joining with `by = join_by(NMF, Gene)`
NMF_corr_thresholding
#NMF_corr_thresholding %>% write_csv("NMF_GeneCorr_Thresholding.csv")
LS0tCnRpdGxlOiAiTWFya2VyIEdlbmUgU2VsZWN0aW9uIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpMZXQncyB0cmlhbmd1bGF0ZSBtYXJrZXIgZ2VuZXMgZm9yIGVhY2ggb2YgdGhlIGxpbmVhZ2VzIHRoYXQgd2UgZmluZCBlbnJpY2htZW50IGluLiAKV2UgaGF2ZSB0aHJlZSBzb3VyY2VzIG9mIGluZm9ybWF0aW9uOiAKCiAgMSkgTWFya2VyIGdlbmVzIGJ5IERFU2VxIG9uIHBzZXVkb2J1bGsgc2FtcGxlcyAKICAyKSBDb3JyZWxhdGlvbiB3aXRoIGFidW5kYW5jZSBhY3Jvc3MgODkgc2FtcGxlcwoKTGV0J3MgdGFrZSB0aGUgSW50ZXJzZWN0aW9uIHdoZXJlIHdlIHRha2UgdGhlIGludGVyc2VjdCBvZiBsb29zZWx5IGZpbHRlcmVkIHJlc3VsdHMgbXVsdGlwbGUgYXBwcm9hY2hlcwoKVGhlc2UgZ2VuZXMgd2lsbCBiZSB1c2VkIGFzIGEgZmVhdHVyZSBzcGFjZSBmb3IgbWFudWFsIHBzZXVkb2J1bGstYmFzZWQgcmVncmVzc2lvbgogIAoqKk4uQi4gdGhlc2UgYXJlIGxpbmVhZ2VzIGRlcml2ZWQgZnJvbSBjb25zZW5zdXMgY2x1c3RlcmluZyBvbiBOTUYgd2VpZ2h0cyoqCiAgKipUaGlzIGRpbWVuc2lvbmFsaXR5IHJlZHVjdGlvbiBhcHByb2FjaCBhY3R1YWxseSBsZWFkIHRvIGNsZWFuZXIgY2x1c3RlcmluZyBvZiBjZWxsdHlwZXMgdGhhbiBqdXN0IGNsdXN0ZXJpbmcgY2VsbHR5cGVzIGJ5IGNvcnJlbGF0aW9uKioKICAKCiMjIFNldHVwIAoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGdncHVicikKYGBgCgpDb2RlIGZyb20gQVVDZWxsIGZvciBhZGFwdGl2ZSB0aHJlc2hvbGRpbmcgCgpgYGB7cn0KbGlicmFyeShBVUNlbGwpCgojIyMgSGVscGVyIGZ1bmN0aW9uIGZvciBBVUMgdGhyZXNob2xkaW5nIGZyb20gQVVDZWxsCmdldF9UaHJlc2hvbGQgPC0gZnVuY3Rpb24gKGF1YywgZ1NldE5hbWUsIHBsb3RIaXN0ID0gVFJVRSwgc21hbGxlc3RQb3BQZXJjZW50ID0gMC4yNSwgCiAgICAgICAgICBkZW5zQWRqdXN0ID0gMiwgdGhyUCA9IDAuMDEsIG5CcmVha3MgPSAxMDApIAp7CiAgYXVjUm93IDwtIHQoYXMubWF0cml4KGF1YykpCiAgI2dTZXROYW1lIDwtIHJvd25hbWVzKGF1Y1JvdylbMV0KICBuQ2VsbHMgPC0gbGVuZ3RoKGF1YykKICBza2lwR2xvYmFsIDwtIFRSVUUKICBza2lwUmVkIDwtIEZBTFNFCiAgc2tpcFNtYWxsRGVucyA8LSBGQUxTRQogIGNvbW1lbnRNc2cgPC0gIiIKICBhdWNUaHJzIDwtIGMoKQogIG5vdFBvcFBlcmNlbnQgPC0gMSAtIHNtYWxsZXN0UG9wUGVyY2VudAogIGlmIChzdW0oYXVjID09IDApID4gKG5DZWxscyAqIG5vdFBvcFBlcmNlbnQpKSB7CiAgICBza2lwR2xvYmFsIDwtIEZBTFNFCiAgICBjb21tZW50TXNnIDwtIHBhc3RlKGNvbW1lbnRNc2csIHJvdW5kKChzdW0oYXVjID09IDApL25DZWxscykgKiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAxMDApLCAiJSAobW9yZSB0aGFuICIsIG5vdFBvcFBlcmNlbnQsICIlKSBvZiBBVUMgYXJlIHplcm8uICIsIAogICAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIikKICB9CiAgbWVhbkFVQyA8LSBtZWFuKGF1YykKICBzZEFVQyA8LSBzZChhdWMpCiAgbWF5YmVOb3JtYWxEaXN0ciA8LSAhc3VwcHJlc3NXYXJuaW5ncyhrcy50ZXN0KGF1Yywgcm5vcm0obWF4KDEwMCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxlbmd0aChhdWMpKSwgbWVhbiA9IG1lYW5BVUMsIHNkID0gc2RBVUMpLCBhbHRlcm5hdGl2ZSA9ICJsZXNzIikkcC52YWx1ZSA8IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAwLjAxKQogIGlmIChtYXliZU5vcm1hbERpc3RyKSB7CiAgICBjb21tZW50TXNnIDwtIHBhc3RlMChjb21tZW50TXNnLCAiVGhlIEFVQyBtaWdodCBmb2xsb3cgYSBub3JtYWwgZGlzdHJpYnV0aW9uIChyYW5kb20gZ2VuZS1zZXQ/KS4gIikKICAgIHNraXBHbG9iYWwgPC0gRkFMU0UKICAgIGF1Y1RocnNbIm91dGxpZXJPZkdsb2JhbCJdIDwtIHFub3JtKDEgLSAodGhyUC9uQ2VsbHMpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1lYW4gPSBtZWFuQVVDLCBzZCA9IHNkQVVDKQogIH0KICBoaXN0b2dyYW0gPC0gaGlzdChjKDAsIGF1Yy9tYXgoYXVjKSksIGJyZWFrcyA9IDEwMCwgcGxvdCA9IEZBTFNFKSRjb3VudAogIGlmICgoc3VtKGhpc3RvZ3JhbVsxOjVdKS9zdW0oaGlzdG9ncmFtKSkgPj0gbm90UG9wUGVyY2VudCAqIAogICAgICAwLjc1KSB7CiAgICBza2lwR2xvYmFsIDwtIEZBTFNFCiAgICBza2lwUmVkIDwtIFRSVUUKICAgIHNraXBTbWFsbERlbnMgPC0gVFJVRQogIH0KICBpZiAoKHN1bShoaXN0b2dyYW1bMToxMF0pL3N1bShoaXN0b2dyYW0pKSA+PSBub3RQb3BQZXJjZW50ICogCiAgICAgIDAuNSkgewogICAgc2tpcFNtYWxsRGVucyA8LSBUUlVFCiAgICBza2lwR2xvYmFsIDwtIEZBTFNFCiAgICBhdWNUaHJzWyJ0ZW5QZXJjZW50T2ZNYXgiXSA8LSBtYXgoYXVjKSAqIDAuMQogIH0KICBkZW5zQ3VydmUgPC0gZGVuc2l0eShhdWMsIGFkanVzdCA9IGRlbnNBZGp1c3QsIGN1dCA9IDApCiAgbWF4aW11bXNEZW5zIDwtIE5VTEwKICBpbmZsUG9pbnRzIDwtIGRpZmYoc2lnbihkaWZmKGRlbnNDdXJ2ZSR5KSkpCiAgbWF4aW11bXNEZW5zIDwtIHdoaWNoKGluZmxQb2ludHMgPT0gLTIpCiAgZ2xvYmFsTWF4IDwtIG1heGltdW1zRGVuc1t3aGljaC5tYXgoZGVuc0N1cnZlJHlbbWF4aW11bXNEZW5zXSldCiAgbWluaW11bURlbnMgPC0gd2hpY2goaW5mbFBvaW50cyA9PSAyKQogIHNtYWxsTWluIDwtIE5VTEwKICBpZiAoIXNraXBTbWFsbERlbnMpIAogICAgc21hbGxNaW4gPC0gZGF0YS50YWJsZTo6bGFzdChtaW5pbXVtRGVuc1t3aGljaChtaW5pbXVtRGVucyA8IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdsb2JhbE1heCldKQogIG1pbmltdW1EZW5zIDwtIGMoc21hbGxNaW4sIG1pbmltdW1EZW5zW3doaWNoKG1pbmltdW1EZW5zID4gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnbG9iYWxNYXgpXSkKICBkZW5zVHJoIDwtIE5VTEwKICBpZiAobGVuZ3RoKG1pbmltdW1EZW5zKSA+IDApIHsKICAgIGRlbnNUcmggPC0gZGVuc0N1cnZlJHhbbWluKG1pbmltdW1EZW5zKV0KICAgIGlmIChsZW5ndGgobWF4aW11bXNEZW5zKSA+IDApIHsKICAgICAgbmV4dE1heHMgPC0gbWF4aW11bXNEZW5zW3doaWNoKGRlbnNDdXJ2ZSR4W21heGltdW1zRGVuc10gPiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVuc1RyaCldCiAgICAgIGlmICgobWF4KGRlbnNDdXJ2ZSR5W25leHRNYXhzXSkvbWF4KGRlbnNDdXJ2ZSR5KSkgPCAKICAgICAgICAgIDAuMDUpIHsKICAgICAgICBkZW5zVHJoIDwtIE5VTEwKICAgICAgfQogICAgfQogIH0KICBhdWMgPC0gc29ydChhdWMpCiAgZGlzdHJzIDwtIGxpc3QoKQogIGRpc3Ryc1tbIkdsb2JhbF9rMSJdXSA8LSBsaXN0KG11ID0gYyhtZWFuQVVDLCBOQSksIHNpZ21hID0gYyhzZEFVQywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIE5BKSwgeCA9IGF1YykKICBpZiAoIm1peHRvb2xzIiAlaW4lIHJvd25hbWVzKGluc3RhbGxlZC5wYWNrYWdlcygpKSkgewogICAgbmEgPC0gY2FwdHVyZS5vdXRwdXQoZGlzdHJzW1siazIiXV0gPC0gdHJ5Q2F0Y2gobWl4dG9vbHM6Om5vcm1hbG1peEVNKGF1YywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZmFzdCA9IEZBTFNFLCBrID0gMiwgdmVyYiA9IEZBTFNFKSwgZXJyb3IgPSBmdW5jdGlvbihlKSB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZXR1cm4oTlVMTCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9KSkKICAgIG5hIDwtIGNhcHR1cmUub3V0cHV0KGRpc3Ryc1tbImszIl1dIDwtIHRyeUNhdGNoKG1peHRvb2xzOjpub3JtYWxtaXhFTShhdWMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZhc3QgPSBGQUxTRSwgayA9IDMsIHZlcmIgPSBGQUxTRSksIGVycm9yID0gZnVuY3Rpb24oZSkgewogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuKE5VTEwpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSkpCiAgICBpZiAoaXMubnVsbChkaXN0cnNbWyJrMiJdXSkgJiYgaXMubnVsbChkaXN0cnNbWyJrMyJdXSkpIHsKICAgICAgaWYgKHN1bShhdWMgPT0gMCkgPCAobkNlbGxzICogbm90UG9wUGVyY2VudCAqIDAuNSkpIAogICAgICAgIHNraXBHbG9iYWwgPC0gRkFMU0UKICAgIH0KICAgIGlmICghaXMubnVsbChkaXN0cnNbWyJrMiJdXSkpIHsKICAgICAgY29tcEwgPC0gd2hpY2gubWluKGRpc3Ryc1tbImsyIl1dW1sibXUiXV0pCiAgICAgIGNvbXBSIDwtIHdoaWNoLm1heChkaXN0cnNbWyJrMiJdXVtbIm11Il1dKQogICAgICBoZWlnaHQxIDwtIDAuNC9kaXN0cnNbWyJrMiJdXVtbInNpZ21hIl1dW2NvbXBMXSAqIAogICAgICAgIGRpc3Ryc1tbImsyIl1dW1sibGFtYmRhIl1dW2NvbXBMXQogICAgICBoZWlnaHQyIDwtIDAuNC9kaXN0cnNbWyJrMiJdXVtbInNpZ21hIl1dW2NvbXBSXSAqIAogICAgICAgIGRpc3Ryc1tbImsyIl1dW1sibGFtYmRhIl1dW2NvbXBSXQogICAgICB0YWxsZXIgPC0gaGVpZ2h0MSA8IGhlaWdodDIKICAgICAgZ2xvYmFsSW5jbEluRmlyc3QgPC0gKGRpc3Ryc1tbIkdsb2JhbF9rMSJdXSRtdVsxXSA8IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoZGlzdHJzW1siazIiXV1bWyJtdSJdXVtjb21wTF0gKyAoMS41ICogZGlzdHJzW1siazIiXV1bWyJzaWdtYSJdXVtjb21wTF0pKSkKICAgICAgaW5jbHVkZWRJbkdsb2JhbCA8LSAoKGRpc3Ryc1tbImsyIl1dW1sibXUiXV1bY29tcExdID4gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIChkaXN0cnNbWyJHbG9iYWxfazEiXV0kbXVbMV0gLSBkaXN0cnNbWyJHbG9iYWxfazEiXV0kc2lnbWFbMV0pKSAmJiAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoZGlzdHJzW1siazIiXV1bWyJtdSJdXVtjb21wUl0gPCAoZGlzdHJzW1siR2xvYmFsX2sxIl1dJG11WzFdICsgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGlzdHJzW1siR2xvYmFsX2sxIl1dJHNpZ21hWzFdKSkpCiAgICAgIGlmICh0YWxsZXIgfHwgKGdsb2JhbEluY2xJbkZpcnN0ICYmIGluY2x1ZGVkSW5HbG9iYWwpKSB7CiAgICAgICAgc2tpcEdsb2JhbCA8LSBGQUxTRQogICAgICAgIGlmIChnbG9iYWxJbmNsSW5GaXJzdCAmJiBpbmNsdWRlZEluR2xvYmFsKSAKICAgICAgICAgIGNvbW1lbnRNc2cgPC0gcGFzdGUoY29tbWVudE1zZywgIlRoZSBnbG9iYWwgZGlzdHJpYnV0aW9uIG92ZXJsYXBzIHRoZSBwYXJ0aWFsIGRpc3RyaWJ1dGlvbnMuICIpCiAgICAgICAgaWYgKHRhbGxlciAmJiAhaW5jbHVkZWRJbkdsb2JhbCkgCiAgICAgICAgICBjb21tZW50TXNnIDwtIHBhc3RlKGNvbW1lbnRNc2csICJUaGUgcmlnaHQgZGlzdHJpYnV0aW9uIGlzIHRhbGxlci4gIikKICAgICAgfQogICAgfQogIH0KICBlbHNlIHsKICAgIHdhcm5pbmcoIlBhY2thZ2UgJ21peHRvb2xzJyBpcyBub3QgYXZhaWxhYmxlIHRvIGNhbGN1bGF0ZSB0aGUgc3ViLWRpc3RyaWJ1dGlvbnMuIikKICB9CiAgZ2xQcm9iIDwtIDEgLSAodGhyUC9uQ2VsbHMgKyBzbWFsbGVzdFBvcFBlcmNlbnQpCiAgYXVjVGhyc1siR2xvYmFsX2sxIl0gPC0gcW5vcm0oZ2xQcm9iLCBtZWFuID0gZGlzdHJzW1siR2xvYmFsX2sxIl1dW1sibXUiXV1bMV0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkID0gZGlzdHJzW1siR2xvYmFsX2sxIl1dW1sic2lnbWEiXV1bMV0pCiAgaWYgKCFpcy5udWxsKGRpc3Ryc1tbImsyIl1dKSkgewogICAgazJfTCA8LSB3aGljaC5taW4oZGlzdHJzW1siazIiXV1bWyJtdSJdXSkKICAgIGF1Y1RocnNbIkxfazIiXSA8LSBxbm9ybSgxIC0gKHRoclAvbkNlbGxzKSwgbWVhbiA9IGRpc3Ryc1tbImsyIl1dW1sibXUiXV1bazJfTF0sIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkID0gZGlzdHJzW1siazIiXV1bWyJzaWdtYSJdXVtrMl9MXSkKICB9CiAgaWYgKCFpcy5udWxsKGRpc3Ryc1tbImszIl1dKSkgewogICAgazNfUiA8LSB3aGljaC5tYXgoZGlzdHJzW1siazMiXV1bWyJtdSJdXSkKICAgIGszX1JfdGhyZXNob2xkIDwtIHFub3JtKHRoclAsIG1lYW4gPSBkaXN0cnNbWyJrMyJdXVtbIm11Il1dW2szX1JdLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNkID0gZGlzdHJzW1siazMiXV1bWyJzaWdtYSJdXVtrM19SXSkKICAgIGlmIChrM19SX3RocmVzaG9sZCA+IDApIAogICAgICBhdWNUaHJzWyJSX2szIl0gPC0gazNfUl90aHJlc2hvbGQKICB9CiAgaWYgKCFpcy5udWxsKGRlbnNUcmgpKSB7CiAgICBhdWNUaHJzWyJtaW5pbXVtRGVucyJdIDwtIGRlbnNUcmgKICB9CiAgYXVjVGhyIDwtIGF1Y1RocnMKICBpZiAoc2tpcEdsb2JhbCkgCiAgICBhdWNUaHIgPC0gYXVjVGhyc1t3aGljaCghbmFtZXMoYXVjVGhycykgJWluJSAiR2xvYmFsX2sxIildCiAgaWYgKHNraXBSZWQpIAogICAgYXVjVGhyIDwtIGF1Y1RocnNbd2hpY2goIW5hbWVzKGF1Y1RocnMpICVpbiUgIkxfazIiKV0KICBhdWNUaHIgPC0gYXVjVGhyW3doaWNoLm1heChhdWNUaHIpXQogIGlmICgobGVuZ3RoKGF1Y1RocikgPiAwKSAmJiAobmFtZXMoYXVjVGhyKSA9PSAibWluaW11bURlbnMiKSkgewogICAgbWF4aW11bXNEZW5zIDwtIG1heGltdW1zRGVuc1t3aGljaChkZW5zQ3VydmUkeVttYXhpbXVtc0RlbnNdID4gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMSldCiAgICBpZiAobGVuZ3RoKG1heGltdW1zRGVucykgPiAyKSB7CiAgICAgIHRtcCA8LSBjYmluZChtaW5pbXVtRGVuc1tzZXFfbGVuKGxlbmd0aChtYXhpbXVtc0RlbnMpIC0gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMSldLCBtYXhpbXVtc0RlbnNbLTFdKQogICAgICBGQ3MgPC0gZGVuc0N1cnZlJHlbdG1wWywgMl1dL2RlbnNDdXJ2ZSR5W3RtcFssIDFdXQogICAgICBpZiAoYW55KEZDcyA+IDEuNSkpIAogICAgICAgIHdhcm5pbmcoZ1NldE5hbWUsICI6XHRDaGVjayB0aGUgQVVDIGhpc3RvZ3JhbS4gIiwgCiAgICAgICAgICAgICAgICAiJ21pbmltdW1EZW5zJyB3YXMgc2VsZWN0ZWQgYXMgdGhlIGJlc3QgdGhyZXNob2xkLCAiLCAKICAgICAgICAgICAgICAgICJidXQgdGhlcmUgbWlnaHQgYmUgc2V2ZXJhbCBkaXN0cmlidXRpb25zIGluIHRoZSBBVUMuIikKICAgIH0KICB9CiAgaWYgKCJtaW5pbXVtRGVucyIgJWluJSBuYW1lcyhhdWNUaHJzKSkgCiAgICBhdWNUaHIgPC0gYXVjVGhyc1sibWluaW11bURlbnMiXQogIGlmIChsZW5ndGgoYXVjVGhyKSA9PSAwKSAKICAgIGF1Y1RociA8LSBhdWNUaHJzW3doaWNoLm1heChhdWNUaHJzKV0KICBpZiAobGVuZ3RoKGF1Y1RocikgPT0gMCkgCiAgICBhdWNUaHIgPC0gMQogIGlmIChsZW5ndGgoYXVjVGhyKSA+IDEpIAogICAgYXVjVGhyIDwtIHVubGlzdChhdWNUaHJbd2hpY2gubWF4KGF1Y1RocildKQogIGlmIChwbG90SGlzdCkgewogICAgaGlzdEluZm8gPC0gQVVDZWxsX3Bsb3RIaXN0KGF1Y1JvdywgYXVjVGhyID0gYXVjVGhyLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuQnJlYWtzID0gbkJyZWFrcykKICAgIGhpc3RNYXggPC0gbWF4KGhpc3RJbmZvW1tnU2V0TmFtZV1dJGNvdW50cykKICAgIGRlbnNDdXJ2ZSR5IDwtIGRlbnNDdXJ2ZSR5ICogKGhpc3RNYXgvbWF4KGRlbnNDdXJ2ZSR5KSkKICAgIHRoaXNMd2QgPC0gaWZlbHNlKChhdWNUaHJzWyJtaW5pbXVtRGVucyJdID09IGF1Y1RocikgJiYgCiAgICAgICAgICAgICAgICAgICAgICAgICghaXMubnVsbChhdWNUaHIpICYmICFpcy5udWxsKGF1Y1RocnNbIm1pbmltdW1EZW5zIl0pKSwgCiAgICAgICAgICAgICAgICAgICAgICAzLCAxKQogICAgbGluZXMoZGVuc0N1cnZlLCBsdHkgPSAxLCBsd2QgPSB0aGlzTHdkLCBjb2wgPSAiYmx1ZSIpCiAgICBpZiAoIWlzLm51bGwobWluaW11bURlbnMpKSAKICAgICAgcG9pbnRzKGRlbnNDdXJ2ZSR4W21pbmltdW1EZW5zXSwgZGVuc0N1cnZlJHlbbWluaW11bURlbnNdLCAKICAgICAgICAgICAgIHBjaCA9IDE2LCBjb2wgPSAiZGFya2JsdWUiKQogICAgc2NhbEZhY3QgPC0gMQogICAgYXVjRGlzdHIgPC0gZG5vcm0oZGlzdHJzW1siR2xvYmFsX2sxIl1dW1sieCJdXSwgbWVhbiA9IGRpc3Ryc1tbIkdsb2JhbF9rMSJdXVtbIm11Il1dWzFdLCAKICAgICAgICAgICAgICAgICAgICAgIHNkID0gZGlzdHJzW1siR2xvYmFsX2sxIl1dW1sic2lnbWEiXV1bMV0pCiAgICBzY2FsRmFjdCA8LSAoaGlzdE1heC9tYXgoYXVjRGlzdHIpKSAqIDAuOTUKICAgIHRoaXNMd2QgPC0gaWZlbHNlKGF1Y1RocnNbIkdsb2JhbF9rMSJdID09IGF1Y1RociwgMywgCiAgICAgICAgICAgICAgICAgICAgICAxKQogICAgbGluZXMoZGlzdHJzW1siR2xvYmFsX2sxIl1dW1sieCJdXSwgc2NhbEZhY3QgKiBhdWNEaXN0ciwgCiAgICAgICAgICBjb2wgPSAiZGFya2dyZXkiLCBsd2QgPSB0aGlzTHdkLCBsdHkgPSAyKQogICAgaWYgKCFpcy5udWxsKGRpc3Ryc1tbImsyIl1dKSkgewogICAgICBhdWNEaXN0ciA8LSBkbm9ybShkaXN0cnNbWyJrMiJdXVtbIngiXV0sIG1lYW4gPSBkaXN0cnNbWyJrMiJdXVtbIm11Il1dW2syX0xdLCAKICAgICAgICAgICAgICAgICAgICAgICAgc2QgPSBkaXN0cnNbWyJrMiJdXVtbInNpZ21hIl1dW2syX0xdKQogICAgICBzY2FsRmFjdCA8LSAoaGlzdE1heC9tYXgoYXVjRGlzdHIpKSAqIDAuOTUKICAgICAgdGhpc0x3ZCA8LSBpZmVsc2UoYXVjVGhyc1siazIiXSA9PSBhdWNUaHIsIDMsIDEpCiAgICAgIGxpbmVzKGRpc3Ryc1tbImsyIl1dW1sieCJdXSwgc2NhbEZhY3QgKiBhdWNEaXN0ciwgCiAgICAgICAgICAgIGNvbCA9ICJyZWQiLCBsd2QgPSB0aGlzTHdkLCBsdHkgPSAyKQogICAgICByZWN0KGRpc3Ryc1tbImsyIl1dW1sibXUiXV1bazJfTF0gLSBkaXN0cnNbWyJrMiJdXVtbInNpZ21hIl1dW2syX0xdLCAKICAgICAgICAgICBoaXN0TWF4IC0gKGhpc3RNYXggKiAwLjAyKSwgZGlzdHJzW1siazIiXV1bWyJtdSJdXVtrMl9MXSArIAogICAgICAgICAgICAgZGlzdHJzW1siazIiXV1bWyJzaWdtYSJdXVtrMl9MXSwgaGlzdE1heCwgY29sID0gIiM3MDAwMDAzMCIsIAogICAgICAgICAgIGJvcmRlciA9ICIjMDAwMDkwMDAiKQogICAgfQogICAgaWYgKCghaXMubnVsbChkaXN0cnNbWyJrMyJdXSkpICYmICgiUl9rMyIgJWluJSBuYW1lcyhhdWNUaHJzKSkpIHsKICAgICAgazNfTCA8LSB3aGljaC5taW4oZGlzdHJzW1siazMiXV1bWyJtdSJdXSkKICAgICAgYXVjRGlzdHIyIDwtIGRub3JtKGRpc3Ryc1tbImszIl1dW1sieCJdXSwgbWVhbiA9IGRpc3Ryc1tbImszIl1dW1sibXUiXV1bazNfUl0sIAogICAgICAgICAgICAgICAgICAgICAgICAgc2QgPSBkaXN0cnNbWyJrMyJdXVtbInNpZ21hIl1dW2szX1JdKQogICAgICBzY2FsRmFjdDIgPC0gc2NhbEZhY3QgKiAoZGlzdHJzW1siazMiXV1bWyJsYW1iZGEiXV1bazNfUl0vZGlzdHJzW1siazMiXV1bWyJsYW1iZGEiXV1bazNfTF0pCiAgICAgIHRoaXNMd2QgPC0gaWZlbHNlKGF1Y1RocnNbImszIl0gPT0gYXVjVGhyLCAzLCAxKQogICAgICBsaW5lcyhkaXN0cnNbWyJrMyJdXVtbIngiXV0sIHNjYWxGYWN0MiAqIGF1Y0Rpc3RyMiwgCiAgICAgICAgICAgIGNvbCA9ICJtYWdlbnRhIiwgbHdkID0gdGhpc0x3ZCwgbHR5ID0gMikKICAgICAgcmVjdChkaXN0cnNbWyJrMyJdXVtbIm11Il1dW2szX1JdIC0gZGlzdHJzW1siazMiXV1bWyJzaWdtYSJdXVtrM19SXSwgCiAgICAgICAgICAgaGlzdE1heCAtIChoaXN0TWF4ICogMC4wMiksIGRpc3Ryc1tbImszIl1dW1sibXUiXV1bazNfUl0gKyAKICAgICAgICAgICAgIGRpc3Ryc1tbImszIl1dW1sic2lnbWEiXV1bazNfUl0sIGhpc3RNYXgsIGNvbCA9ICIjODA4MDgwMzAiLCAKICAgICAgICAgICBib3JkZXIgPSAiIzgwODA4MDMwIikKICAgIH0KICAgIGF1Y1RocnMgPC0gYXVjVGhyc1shaXMubmEoYXVjVGhycyldCiAgICBpZiAobGVuZ3RoKGF1Y1RocnMpID4gMCkgewogICAgICBwYXJzIDwtIGxpc3QoKQogICAgICBwYXJzW1siR2xvYmFsX2sxIl1dIDwtIGMoY29sMSA9ICIjOTA5MDkwIiwgY29sMiA9ICJibGFjayIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zID0gMC45KQogICAgICBwYXJzW1siTF9rMiJdXSA8LSBjKGNvbDEgPSAicmVkIiwgY29sMiA9ICJkYXJrcmVkIiwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgcG9zID0gMC44KQogICAgICBwYXJzW1siUl9rMyJdXSA8LSBjKGNvbDEgPSAibWFnZW50YSIsIGNvbDIgPSAibWFnZW50YSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgIHBvcyA9IDAuNikKICAgICAgcGFyc1tbIm1pbmltdW1EZW5zIl1dIDwtIGMoY29sMSA9ICJibHVlIiwgY29sMiA9ICJkYXJrYmx1ZSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb3MgPSAwLjQpCiAgICAgIHBhcnNbWyJ0ZW5QZXJjZW50T2ZNYXgiXV0gPC0gYyhjb2wxID0gImRhcmtncmVlbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sMiA9ICJkYXJrZ3JlZW4iLCBwb3MgPSAwLjkpCiAgICAgIHBhcnNbWyJvdXRsaWVyT2ZHbG9iYWwiXV0gPC0gYyhjb2wxID0gImRhcmtncmVlbiIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sMiA9ICJkYXJrZ3JlZW4iLCBwb3MgPSAwLjkpCiAgICAgIGZvciAodGhyIGluIG5hbWVzKGF1Y1RocnMpKSB7CiAgICAgICAgdGhpc0x3ZCA8LSBpZmVsc2UoYXVjVGhyc1t0aHJdID09IGF1Y1RociwgNSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgMikKICAgICAgICB0aGlzTHR5IDwtIGlmZWxzZShhdWNUaHJzW3Rocl0gPT0gYXVjVGhyLCAxLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAzKQogICAgICAgIGFibGluZSh2ID0gYXVjVGhyc1t0aHJdLCBjb2wgPSBwYXJzW1t0aHJdXVsxXSwgCiAgICAgICAgICAgICAgIGx3ZCA9IHRoaXNMd2QsIGx0eSA9IHRoaXNMdHkpCiAgICAgICAgeFBvcyA8LSBhdWNUaHJzW3Rocl0gKiAxLjAxCiAgICAgICAgaWYgKGF1Y1RocnNbdGhyXSA+IChtYXgoYXVjKSAqIDAuOCkpIAogICAgICAgICAgeFBvcyA8LSAwCiAgICAgICAgaWYgKGF1Y1RocnNbdGhyXSA9PSBhdWNUaHIpIAogICAgICAgICAgdGV4dCh4UG9zLCBoaXN0TWF4ICogYXMubnVtZXJpYyhwYXJzW1t0aHJdXVszXSksIAogICAgICAgICAgICAgICBwb3MgPSA0LCBjb2wgPSBwYXJzW1t0aHJdXVsyXSwgY2V4ID0gMC44LCAKICAgICAgICAgICAgICAgcGFzdGUoIkFVQyA+ICIsIHNpZ25pZihhdWNUaHJzW3Rocl0sIDIpLCAKICAgICAgICAgICAgICAgICAgICAgIlxuKCIsIHN1bShhdWMgPiBhdWNUaHJzW3Rocl0pLCAiIGNlbGxzKSIsIAogICAgICAgICAgICAgICAgICAgICBzZXAgPSAiIikpCiAgICAgIH0KICAgIH0KICB9CiAgcmV0dXJuKGxpc3Qoc2VsZWN0ZWQgPSBhdWNUaHIsIHRocmVzaG9sZHMgPSBjYmluZCh0aHJlc2hvbGQgPSBhdWNUaHJzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5DZWxscyA9IHNhcHBseShhdWNUaHJzLCBmdW5jdGlvbih4KSBzdW0oYXVjID4geCkpKSwgCiAgICAgICAgICAgICAgY29tbWVudCA9IGNvbW1lbnRNc2cpKQp9CgpgYGAKCgojIyBERSBHZW5lcwoKYGBge3J9CmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgpgYGB7cn0KTGluZWFnZV9ERSA8LSByZWFkX2NzdigiQkFMTF9ERXJlc3VsdHNfTk1GX0xpbmVhZ2UuY3N2IikKTGluZWFnZV9ERQpgYGAKCmBgYHtyfQpMaW5lYWdlX0RFICU+JSAKICBmaWx0ZXIocGFkaiA8IDAuMDEpICU+JSAKICBwdWxsKExpbmVhZ2UpICU+JSB0YWJsZSgpICU+JSBzb3J0KGRlY3JlYXNpbmcgPSBUKQpgYGAKCgojIyBGb3IgTk1GIHNjb3JlIFF1YW50aWZpY2F0aW9uCgpQZWFyc29uIGNvcnJlbGF0aW9uIFZTVCBOTUYgc3RyaW5nZW50CiAgSW50ZXJzZWN0IG9uIFBzZXVkb2J1bGsgREUgRkRSIDAuMDUKICAKT3IgTEFTU08gcmVncmVzc2lvbgogIFBlYXJzb24gY29ycmVsYXRpb24gc3RyaW5nZW50IFZTVCBOTUYgKyBQc2V1ZG9idWxrIERFIHN0cmluZ2VudAogIAoqKlNldCBhZGFwdGl2ZSB0aHJlc2hvbGRzKiogIApUaHMgaXMgdGhlIHBlYXJzb24gY29ycmVsYXRpb24gYmV0d2VlbiBWU1Qtbm9ybWFsaXplZCBnZW5lIGV4cHJlc3Npb24gYW5kIHRoZSBzY29yZSBmb3IgZWFjaCBOTUYgbGluZWFnZQoKYGBge3J9Ck5NRl9jb3JyIDwtIGRhdGEudGFibGU6OmZyZWFkKCdOTUZfZ2VuZV9jb3JyLmNzdicpICU+JSBzZWxlY3QoLVYxKQpOTUZfY29ycgpgYGAKCmBgYHtyfQpOTUZfY29yciAlPiUgCiAgZmlsdGVyKHF2YWx1ZSA8IDAuMDUsIHBlYXJzb24gPiAwKSAlPiUgCiAgcHVsbChOTUYpICU+JSB0YWJsZSgpICU+JSBzb3J0KGRlY3JlYXNpbmcgPSBUUlVFKQpgYGAKCiMjIyMjIFNldCBQb3NpdGl2ZSBDb3JyZWxhdGlvbiBUaHJlc2hvbGRzCgpgYGB7cn0KTk1GX2NvcnJfdGhyZXNob2xkcyA9IGRhdGEuZnJhbWUoKQoKZm9yKGxpbiBpbiB1bmlxdWUoTk1GX2NvcnIkTk1GKSl7CiAgdGhyZXNob2xkcyA9IGdldF9UaHJlc2hvbGQoTk1GX2NvcnIgJT4lIGZpbHRlcihOTUYgPT0gbGluLCBxdmFsdWUgPCAwLjA1LCBwZWFyc29uID4gMCkgJT4lIHB1bGwocGVhcnNvbiksIGxpbikkdGhyZXNob2xkcwogIE5NRl9jb3JyX3RocmVzaG9sZHMgPSAKICAgIGJpbmRfcm93cygKICAgICAgTk1GX2NvcnJfdGhyZXNob2xkcywKICAgICAgZGF0YS5mcmFtZSgKICAgICAgICAnTk1GJyA9IGxpbiwKICAgICAgICAnSzFfdGhyZXNob2xkJyA9IHRocmVzaG9sZHNbJ0dsb2JhbF9rMScsJ3RocmVzaG9sZCddLAogICAgICAgICdLMl90aHJlc2hvbGQnID0gdGhyZXNob2xkc1snTF9rMicsJ3RocmVzaG9sZCddCiAgICApKQp9CmBgYAoKClZpc3VhbGl6ZSB0aHJlc2hvbGRpbmcgd2l0aGluIGFsbCBwb3NpdGl2ZSBjb3JyZWxhdGlvbnMKCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD0xMn0KTk1GX2NvcnJfdGhyZXNob2xkcyAlPiUgCiAgbGVmdF9qb2luKE5NRl9jb3JyKSAlPiUgZmlsdGVyKHBlYXJzb24gPiAwKSAlPiUgCiAgbXV0YXRlKE5NRiA9IGZhY3RvcihOTUYsIGxldmVscyA9IGMoJ05NRjYnLCAnTk1GOCcsICdOTUYyJywgJ05NRjEnLCAnTk1GMycsICdOTUY5JywgJ05NRjQnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdOTUY1JywgJ05NRjEwJywgJ05NRjcnKSkpICU+JSAKICBtdXRhdGUodGhyZXNob2xkID0gaWZlbHNlKHBlYXJzb24gPiBLMV90aHJlc2hvbGQsICdwYXNzJywgJ2ZhaWwnKSkgJT4lIAogIGdncGxvdChhZXMoeCA9IHBlYXJzb24sIGZpbGwgPSB0aHJlc2hvbGQpKSArIAogIGdlb21faGlzdG9ncmFtKGJpbnM9MTAwKSArIHRoZW1lX3B1YnIobGVnZW5kID0gJ3RvcCcpICsgCiAgc2NhbGVfZmlsbF9icmV3ZXIocGFsZXR0ZSA9ICdEYXJrMicsIGRpcmVjdGlvbiA9IC0xKSArIAogIGZhY2V0X3dyYXAoLn5OTUYsIHNjYWxlID0gJ2ZyZWUnLCBuY29sPTUpICsgCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdCA9IEsxX3RocmVzaG9sZCksIGx0eT0yKQoKYGBgCgpWaXN1YWxpemUgdGhyZXNob2xkaW5nIHdpdGhpbiBGRFIgPCAwLjA1IGNvcnJlbGF0aW9ucwoKYGBge3IsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTEyfQpOTUZfY29ycl90aHJlc2hvbGRzICU+JSAKICBsZWZ0X2pvaW4oTk1GX2NvcnIpICU+JSBmaWx0ZXIocXZhbHVlIDwgMC4wNSwgcGVhcnNvbiA+IDApICU+JSAKICBtdXRhdGUoTk1GID0gZmFjdG9yKE5NRiwgbGV2ZWxzID0gYygnTk1GNicsICdOTUY4JywgJ05NRjInLCAnTk1GMScsICdOTUYzJywgJ05NRjknLCAnTk1GNCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ05NRjUnLCAnTk1GMTAnLCAnTk1GNycpKSkgJT4lIAogIG11dGF0ZSh0aHJlc2hvbGQgPSBpZmVsc2UocGVhcnNvbiA+IEsxX3RocmVzaG9sZCwgJ3Bhc3MnLCAnZmFpbCcpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcGVhcnNvbiwgZmlsbCA9IHRocmVzaG9sZCkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlucz0xMDApICsgdGhlbWVfcHVicihsZWdlbmQgPSAndG9wJykgKyAKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gJ0RhcmsyJywgZGlyZWN0aW9uID0gLTEpICsgCiAgZmFjZXRfd3JhcCgufk5NRiwgc2NhbGUgPSAnZnJlZScsIG5jb2w9NSkgKyAKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gSzFfdGhyZXNob2xkKSwgbHR5PTIpCgpgYGAKCmBgYHtyfQpOTUZfY29ycl9wb3MgPC0gTk1GX2NvcnJfdGhyZXNob2xkcyAlPiUgCiAgbGVmdF9qb2luKE5NRl9jb3JyKSAlPiUgCiAgbXV0YXRlKHRocmVzaG9sZCA9IGlmZWxzZShwZWFyc29uID4gSzFfdGhyZXNob2xkLCAncGFzcycsICdmYWlsJykpIAoKTk1GX2NvcnJfcG9zICU+JSAKICBmaWx0ZXIocXZhbHVlIDwgMC4wNSwgcGVhcnNvbiA+IDApICU+JSAKICBmaWx0ZXIodGhyZXNob2xkID09ICdwYXNzJykgJT4lIAogIHB1bGwoTk1GKSAlPiUgdGFibGUoKSAlPiUgc29ydChkZWNyZWFzaW5nID0gVCkKYGBgCgojIyMgTmVnYXRpdmUgdGhyZXNob2xkcwoKYGBge3J9Ck5NRl9jb3JyX25lZ190aHJlc2hvbGRzID0gZGF0YS5mcmFtZSgpCgpmb3IobGluIGluIHVuaXF1ZShOTUZfY29yciROTUYpKXsKICB0aHJlc2hvbGRzID0gZ2V0X1RocmVzaG9sZChOTUZfY29yciAlPiUgZmlsdGVyKE5NRiA9PSBsaW4sIHF2YWx1ZSA8IDAuMDUsIHBlYXJzb24gPCAwKSAlPiUgbXV0YXRlKHBlYXJzb24gPSAtcGVhcnNvbikgJT4lIHB1bGwocGVhcnNvbiksIGxpbikkdGhyZXNob2xkcwogIE5NRl9jb3JyX25lZ190aHJlc2hvbGRzID0gCiAgICBiaW5kX3Jvd3MoCiAgICAgIE5NRl9jb3JyX25lZ190aHJlc2hvbGRzLAogICAgICBkYXRhLmZyYW1lKAogICAgICAgICdOTUYnID0gbGluLAogICAgICAgICdLMV90aHJlc2hvbGQnID0gdGhyZXNob2xkc1snR2xvYmFsX2sxJywndGhyZXNob2xkJ10sCiAgICAgICAgJ0syX3RocmVzaG9sZCcgPSB0aHJlc2hvbGRzWydMX2syJywndGhyZXNob2xkJ10KICAgICkpCn0KYGBgCgoKVmlzdWFsaXplIHRocmVzaG9sZGluZyB3aXRoaW4gYWxsIG5lZ2F0aXZlIGNvcnJlbGF0aW9ucwoKYGBge3IsIGZpZy5oZWlnaHQ9MywgZmlnLndpZHRoPTEyfQpOTUZfY29ycl9uZWdfdGhyZXNob2xkcyAlPiUgCiAgbGVmdF9qb2luKE5NRl9jb3JyKSAlPiUgZmlsdGVyKHBlYXJzb24gPCAwKSAlPiUgCiAgbXV0YXRlKG5lZ2F0aXZlX3BlYXJzb24gPSAtcGVhcnNvbikgJT4lIAogIG11dGF0ZShOTUYgPSBmYWN0b3IoTk1GLCBsZXZlbHMgPSBjKCdOTUY2JywgJ05NRjgnLCAnTk1GMicsICdOTUYxJywgJ05NRjMnLCAnTk1GOScsICdOTUY0JywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnTk1GNScsICdOTUYxMScsICdOTUYxMCcsICdOTUY3JykpKSAlPiUgCiAgbXV0YXRlKHRocmVzaG9sZCA9IGlmZWxzZShuZWdhdGl2ZV9wZWFyc29uID4gSzFfdGhyZXNob2xkLCAncGFzcycsICdmYWlsJykpICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBuZWdhdGl2ZV9wZWFyc29uLCBmaWxsID0gdGhyZXNob2xkKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShiaW5zPTEwMCkgKyB0aGVtZV9wdWJyKGxlZ2VuZCA9ICd0b3AnKSArIAogIHNjYWxlX2ZpbGxfYnJld2VyKHBhbGV0dGUgPSAnRGFyazInLCBkaXJlY3Rpb24gPSAtMSkgKyAKICBmYWNldF93cmFwKC5+Tk1GLCBzY2FsZSA9ICdmcmVlJywgbmNvbD02KSArIAogIGdlb21fdmxpbmUoYWVzKHhpbnRlcmNlcHQgPSBLMV90aHJlc2hvbGQpLCBsdHk9MikKCk5NRl9jb3JyX25lZ190aHJlc2hvbGRzCmBgYAoKVmlzdWFsaXplIHRocmVzaG9sZGluZyB3aXRoaW4gRkRSIDwgMC4wNSBjb3JyZWxhdGlvbnMKCmBgYHtyLCBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD0xMn0KTk1GX2NvcnJfbmVnX3RocmVzaG9sZHMgJT4lIAogIGxlZnRfam9pbihOTUZfY29ycikgJT4lIGZpbHRlcihxdmFsdWUgPCAwLjA1LCBwZWFyc29uIDwgMCkgJT4lIAogIG11dGF0ZShuZWdhdGl2ZV9wZWFyc29uID0gLXBlYXJzb24pICU+JSAKICBtdXRhdGUoTk1GID0gZmFjdG9yKE5NRiwgbGV2ZWxzID0gYygnTk1GNicsICdOTUY4JywgJ05NRjInLCAnTk1GMScsICdOTUYzJywgJ05NRjknLCAnTk1GNCcsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ05NRjUnLCAnTk1GMTEnLCAnTk1GMTAnLCAnTk1GNycpKSkgJT4lIAogIG11dGF0ZSh0aHJlc2hvbGQgPSBpZmVsc2UobmVnYXRpdmVfcGVhcnNvbiA+IEsxX3RocmVzaG9sZCwgJ3Bhc3MnLCAnZmFpbCcpKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gbmVnYXRpdmVfcGVhcnNvbiwgZmlsbCA9IHRocmVzaG9sZCkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oYmlucz0xMDApICsgdGhlbWVfcHVicihsZWdlbmQgPSAndG9wJykgKyAKICBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gJ0RhcmsyJywgZGlyZWN0aW9uID0gLTEpICsgCiAgZmFjZXRfd3JhcCgufk5NRiwgc2NhbGUgPSAnZnJlZScsIG5jb2w9NikgKyAKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0ID0gSzFfdGhyZXNob2xkKSwgbHR5PTIpCgpOTUZfY29ycl9uZWdfdGhyZXNob2xkcwpgYGAKCmBgYHtyfQpOTUZfY29ycl9uZWcgPC0gTk1GX2NvcnJfbmVnX3RocmVzaG9sZHMgJT4lIAogIGxlZnRfam9pbihOTUZfY29ycikgJT4lIAogIG11dGF0ZSh0aHJlc2hvbGQgPSBpZmVsc2UocGVhcnNvbiA8IC1LMV90aHJlc2hvbGQsICdwYXNzJywgJ2ZhaWwnKSkgCgpOTUZfY29ycl9uZWcgJT4lIAogIGZpbHRlcihxdmFsdWUgPCAwLjA1LCBwZWFyc29uIDwgMCkgJT4lIAogIGZpbHRlcih0aHJlc2hvbGQgPT0gJ3Bhc3MnKSAlPiUgCiAgcHVsbChOTUYpICU+JSB0YWJsZSgpICU+JSBzb3J0KGRlY3JlYXNpbmcgPSBUKQpgYGAKCmBgYHtyfQpOTUZfY29ycl9uZWcgJT4lIAogIGZpbHRlcihxdmFsdWUgPCAwLjA1LCBwZWFyc29uIDwgMCkgCmBgYAoKYGBge3J9Ck5NRl9jb3JyX3RocmVzaG9sZGluZyA8LSBOTUZfY29ycl9wb3MgJT4lIHNlbGVjdChOTUYsIEdlbmUsIHBlYXJzb24sIHB2YWx1ZSwgcXZhbHVlLCBwb3NfSzFfdGhyZXNob2xkID0gSzFfdGhyZXNob2xkLCBwb3NfdGhyZXNob2xkID0gdGhyZXNob2xkKSAlPiUgCiAgbGVmdF9qb2luKE5NRl9jb3JyX25lZyAlPiUgc2VsZWN0KE5NRiwgR2VuZSwgbmVnX0sxX3RocmVzaG9sZCA9IEsxX3RocmVzaG9sZCwgbmVnX3RocmVzaG9sZCA9IHRocmVzaG9sZCkpICU+JSAKICBtdXRhdGUobmVnX0sxX3RocmVzaG9sZCA9IC1uZWdfSzFfdGhyZXNob2xkLCB0aHJlc2hvbGQgPSBpZmVsc2UocG9zX3RocmVzaG9sZCA9PSAncGFzcycgfCBuZWdfdGhyZXNob2xkID09ICdwYXNzJywgJ3Bhc3MnLCAnZmFpbCcpKSAlPiUgCiAgc2VsZWN0KE5NRiwgR2VuZSwgcGVhcnNvbiwgcHZhbHVlLCBxdmFsdWUsIHBvc19LMV90aHJlc2hvbGQsIG5lZ19LMV90aHJlc2hvbGQsIHRocmVzaG9sZCkKCk5NRl9jb3JyX3RocmVzaG9sZGluZwpgYGAKCgpgYGB7cn0KI05NRl9jb3JyX3RocmVzaG9sZGluZyAlPiUgd3JpdGVfY3N2KCJOTUZfR2VuZUNvcnJfVGhyZXNob2xkaW5nLmNzdiIpCmBgYAoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCg==